﻿using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic ;
using System.Collections; 

namespace Server_fmf
{
    class Program
    {
        enum msgProtocol { code = 0, myId, msgText, partnerId }
        const string  REQUEST_ID  = "001";
        const string  REQUEST_PARTNER = "010";
        const string LOCATION_PARAMETER = "011";
        const string MAKE_CONNECTION = "101";
        const string DISCONNECT = "000";
        const string READY = "111";

        // dictionnaire des id des utilisateurs connecté avec leur liste de sockets correspondantes 
        static Dictionary<string, List<Socket>> connectedList = new Dictionary<string, List<Socket>>();

        // disctionnaire pour stocket la position et autres information des utilisateur  
        static Dictionary<string, string> Locations = new Dictionary<string, string>(); 
        
        //liste des id des utilisateurs connectés 
        static List<int> ids = new List<int>(); 
        
        // fornir un id random
        private static string GetRandomCode()
        {
            Random rnd = new Random();
            int id = rnd.Next(1, 7000);
            while (ids.Contains(id))
            {
                id = rnd.Next(1, 7000);
            }
            ids.Add(id); 
            return id.ToString(); 
        }

        // envoyer id au utilisateur qui vient de se connecter
        private static  String SendId(object s)
        {
            Socket soc = (Socket)s;
            string id = GetRandomCode(); 
            List<Socket> socketList = new List<Socket>();
            socketList.Add(soc);
            connectedList.Add(id, socketList); 
            soc.Send(formatMsg(REQUEST_ID, id));
            return id; 
        }
       
        // quand un utilisateur demande de seconnecter a un partenaire
        private static void ConnectToPartner(object s, string myId, string partnerId)
        {
            var mySocket = (Socket)s; 
            var mySocketList = connectedList[myId]; 
            var partnerSocketList = new List<Socket>() ;
            int partnerId_int;
            var isNum = Int32.TryParse(partnerId, out partnerId_int);
            if (isNum && ids.Contains(partnerId_int))
            {
                partnerSocketList = connectedList[partnerId];
                var partnerSocket = partnerSocketList[0];
                mySocketList.Add(partnerSocket); // add partner socket to my list
                connectedList[myId] = mySocketList;
                mySocket.Send(formatMsg(MAKE_CONNECTION, "TRUE"));

            }
            else
            {
                // quand il n'y a pas encore de partenaire connecter 
                mySocket.Send(formatMsg(MAKE_CONNECTION, "FALSE"));
            }
        }

        // methode qui permet reccuperer la position du partenaire.
        private static void EchangeLocation(object s, string myId, string partnerId, string location)
        {
            Socket soc = (Socket)s;
            if (Locations.ContainsKey(myId))
                Locations[myId] = location;
            else
                Locations.Add(myId, location); 
            string  msgToReceive = ""; 
            Locations.TryGetValue(partnerId, out msgToReceive) ;
            if (msgToReceive != null)
            {
                soc.Send(formatMsg(LOCATION_PARAMETER, msgToReceive));
            }
            else
            {
                soc.Send(formatMsg(LOCATION_PARAMETER, String.Empty));
            }
           
                 

        }

        // formeter le message a envoyer au utilisateur selon le protocole 
        private static Byte[] formatMsg(string code, string msg)
        {
            string formatedMsg = code + "/" + msg + "/";
            Byte[] code_byte = Encoding.UTF8.GetBytes(formatedMsg);
            return code_byte;
        }

        // recuperer toutes les informations du message dans un tableau 
        private static string [] splittingMsg(Byte[] msgByte)
        {
            var response = Encoding.Default.GetString(msgByte);
            String[] msgTab = response.Split('/'); 
            return msgTab ; 
        }
       
        // permet de fermet la connexion
        private static void Disconnect(object s, string userId)
        {
            
            Socket soc = (Socket)s;
            soc.Send(formatMsg(DISCONNECT, ""));
            var mySocketList = connectedList[userId];
            mySocketList.Remove(mySocketList[1]);
            Locations.Remove(Locations[userId]); 
            connectedList[userId] = mySocketList; 
            Console.WriteLine("Disconnect : "+userId); 
            soc.Shutdown(SocketShutdown.Both);
            soc.Disconnect(true);
            soc.Close(); 
        }

        // cette methode est appellee dans un thread pour gerer la communication client serveur 
        public static void CommumicationHandler(object s)
        {
            Socket soc = (Socket)s;
            string Id="";  
            try
            {
                while (true)
                {

                   
                    byte[] b = new byte[100];
                   
                    int k = soc.Receive(b);         
                        
                    Console.WriteLine("Recieved...");
                    String[] msgTab = splittingMsg(b);
                    string code = msgTab[(int)msgProtocol.code] ;
                    if (code == REQUEST_ID)
                        Id = SendId(soc);
                    else if (code == REQUEST_PARTNER)
                        ConnectToPartner(soc, msgTab[(int)msgProtocol.myId], msgTab[(int)msgProtocol.msgText]);
                    else if (code == LOCATION_PARAMETER)
                    {
                        EchangeLocation(soc, msgTab[(int)msgProtocol.myId], msgTab[(int)msgProtocol.partnerId], msgTab[(int)msgProtocol.msgText]);
                    }
                    else if (code == READY)
                    { }
                    else if (code == DISCONNECT)
                    {
                        Disconnect(soc, msgTab[(int)msgProtocol.myId]);
                        break;
                    }
                    else
                    {
                        Disconnect(soc, Id); 
                        break; 
                    }
               
                   
                    Console.WriteLine("\nSent code");
                   
                   
                      


                }
            }
            catch (Exception e)
            {
                Console.WriteLine("\nDisconnected");
                soc.Close();
            }
        }

        // retourne l'adresse ip du serveur
        public string getAdress()
        {
            IPHostEntry host;
            string localIP = "?";
            host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    localIP = ip.ToString();
                }
            }
            return localIP;
        }


        static void Main(string[] args)
        {
            try
            {
                var pr = new Program();
                var ip_str  =  pr.getAdress();                
                IPAddress ipAd = IPAddress.Parse(ip_str); 
                /* Initializes the Listener */
                TcpListener myList = new TcpListener(ipAd, 3000);

                /* Start Listeneting at the specified port */
                myList.Start();

                Console.WriteLine("The server is running at port 3000...");
                Console.WriteLine("The local End point is  :" + myList.LocalEndpoint);
                Console.WriteLine("Waiting for a connection.....");
                while (true)
                {

                    Socket s = myList.AcceptSocket();
                    Console.WriteLine("\nConnection accepted from " + s.RemoteEndPoint);

                    var thread = new Thread(Program.CommumicationHandler);
                    thread.Start(s);
                }

               
                myList.Stop();

            }
            catch (Exception e)
            {
                Console.WriteLine("Error..... " + e.StackTrace);
            }	
        }
    }
}
